package com.dly.hope.service.impl;

import com.dly.hope.constant.ExceptionConstant;
import com.dly.hope.constant.LoginType;
import com.dly.hope.constant.RedisConstant;
import com.dly.hope.dao.RoleDao;
import com.dly.hope.dao.UserAccountDao;
import com.dly.hope.dto.EmailSendDto;
import com.dly.hope.model.ro.LoginRo;
import com.dly.hope.model.ro.PromiseRo;
import com.dly.hope.model.ro.RegisterRo;
import com.dly.hope.model.vo.*;
import com.dly.hope.modle.FriendGroup;
import com.dly.hope.modle.UserAccount;
import com.dly.hope.modle.ValidateCode;
import com.dly.hope.pageUtils.Page;
import com.dly.hope.repository.FriendGroupRepository;
import com.dly.hope.repository.TemplateRepository;
import com.dly.hope.repository.UserAccountRepository;
import com.dly.hope.repository.ValidateCodeRepository;
import com.dly.hope.pageUtils.PageRo;
import com.dly.hope.servce.EmailService;
import com.dly.hope.servce.JwtService;
import com.dly.hope.service.UserAccountService;
import com.dly.hope.service.ValidateCodeService;
import com.dly.hope.type.DataStatusEnum;
import com.dly.hope.type.TemplateType;
import com.dly.hope.utils.*;
import com.dly.hope.vo.RoleVo;
import com.dly.hope.vo.UserDTO;
import com.dly.hope.webSocket.WebSocketServer;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author linyu.dai
 * @project myserve
 * @description 用户账号
 * @creat 2022/8/26 14:40:18
 */
@Service
@Slf4j
public class UserAccountServiceImpl implements UserAccountService {

    @Resource
    private UserAccountRepository userAccountRepository;

    @Resource
    private UserAccountDao userAccountDao;

    @Resource
    private RoleDao roleDao;

    @Resource
    private JwtService jwtService;

    @Resource
    private EmailService emailService;

    @Resource
    private ValidateCodeService validateCodeService;

    @Resource
    private TemplateRepository templateRepository;

    @Resource
    private ValidateCodeRepository validateCodeRepository;

    @Resource
    private FriendGroupRepository friendGroupRepository;

    @Value("${email.expired}")
    private Integer expired;

    @Resource
    private RedisUtil redisUtil;

    @Value("${jwt.redisExpireTime}")
    private long redisExpireTime;

    @Value("${jwt.expireTime}")
    private long tokenExpireTime;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginVo login(LoginRo ro) {

        Optional<UserAccount> userAccountOptional= Optional.empty();

        if(LoginType.Account.equals(ro.getLoginType())) {
            //账号登入
            userAccountOptional = userAccountRepository.findTopByAccountNumberAndDataStatus(ro.getAccount(), DataStatusEnum.EFFICIENT.getCode());
        }else if (LoginType.Email.equals(ro.getLoginType())){
            //邮箱登入
            userAccountOptional = userAccountRepository.findByEmailAndDataStatus(ro.getAccount(), DataStatusEnum.EFFICIENT.getCode());

        }

        return userAccountOptional.map(userAccount -> {  //想要返回用map  不返回用ifPresent
            //校验密码
            checkPassword(userAccount, ro);
            //设置本次的登入记录 使用redis
            String lastLogin = UUID.randomUUID().toString();
            // 记录登入人 和 最后一次登入的uuid  和token共存亡
            redisUtil.setStr(userAccount.getId(), lastLogin, tokenExpireTime, TimeUnit.SECONDS);

            //得到token 和获取信息
            UserVo userVo = new UserVo();
            String token = getToken(userAccount, lastLogin, userVo);

            //设置角色名字
            List<RoleVo> roleName = roleDao.getRole(userAccount.getId()).stream().map(role -> new RoleVo(role.getId(),role.getRoleName())).collect(Collectors.toList());
            userVo.setRolas(roleName);

            //存放登入记录 到redis 用来维护redis
            redisUtil.setStr(RedisConstant.TOKEN+token, userVo.getId(), redisExpireTime, TimeUnit.SECONDS);

            LoginVo loginVo = new LoginVo();
            loginVo.setToken(token);
            loginVo.setUserVo(userVo);
            loginVo.setKeepConnection(redisExpireTime);

            WebSocketServer.logOut(userAccount.getId());
            return loginVo;
        }).orElseThrow(() -> ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_NULL));
    }

    /**
     * @Author linyu.dai
     * @Description 检测密码
     * @Date 2022/8/27 20:15
     * @Param [userAccount, ro]
     * @return void
     */
    private void checkPassword(UserAccount userAccount, LoginRo ro){
        String password = MD5Util.encode(ro.getPassword());
        if(!password.equals(userAccount.getPassWord())){
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_PASSWORD_ERROR);
        }
    }

    private String getToken(UserAccount userAccount, String lastLogin, UserVo userVo){
        UserDTO userDTO = new UserDTO();
        userDTO.setId(userAccount.getId());
        userDTO.setLastLogin(lastLogin);

        userVo.setId(userAccount.getId());
        userVo.setAccount(userAccount.getAccountNumber());
        userVo.setEmail(userAccount.getEmail());
        userVo.setName(userAccount.getAccountName());
        userVo.setAvatar(userAccount.getAvatar());
        return jwtService.sign(userDTO);
    }

    @Override
    public void register(RegisterRo registerRo) {
        //校验验证码
        validateCodeService.checkCode(registerRo.getEmail(), registerRo.getVer_code());
        //保存信息
        saveUser(registerRo);
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveUser(RegisterRo registerRo){
        if(userAccountRepository.findByEmailAndDataStatus(registerRo.getEmail(), 1).isPresent()){
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.EMAIL_EXIT);
        }

        if(userAccountRepository.findByAccountNumberAndDataStatus(registerRo.getAccount(), 1).isPresent()){
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.ACCOUNT_EXIT);
        }

        UserAccount userAccount = new UserAccount();

        userAccount.setEmail(registerRo.getEmail());
        userAccount.setAccountNumber(registerRo.getAccount());
        userAccount.setPassWord(MD5Util.encode(registerRo.getPassword()));
        userAccount.setAccountName(registerRo.getAccount_name());

        UserAccount save = userAccountRepository.save(userAccount);

        //创建默认分组
        FriendGroup friendGroup = new FriendGroup();
        friendGroup.setName("默认分组");
        friendGroup.setCreateUser(save.getId());
        friendGroupRepository.save(friendGroup);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void sendCode(String email) {

        if(Pattern.matches("^(\\w+([-.][A-Za-z0-9]+)*){3,18}@\\w+([-.][A-Za-z0-9]+)*\\.\\w+([-.][A-Za-z0-9]+)*$", email)) {
            //先查询已发送 未使用的邮件
            validateCodeRepository.findByEmailToAndUseAndSendAndDataStatus(email, DataStatusEnum.NO_USER.getCode(), true, DataStatusEnum.EFFICIENT.getCode())
                    .ifPresentOrElse(validateCode -> {
                        //过期
                        if (DateUtils.addSeconds(validateCode.getCreateDate(), expired).before(new Date())) {
                            //删除过期
                            validateCode.setDataStatus(DataStatusEnum.INVALID.getCode());
                            send(email);
                        } else {
                            throw ExceptionPropertiesUtil.getException(ExceptionConstant.VALIDATE_CODE_IS_SEND);
                        }
                    }, () -> send(email));
        }else{
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.EMAIL_VALID_ERROR);
        }
    }


    @Override
    public OnLineVo getOnLinePeople() {
        Long setSize = redisUtil.getSetSize(RedisConstant.ONLINE);
        Set<String> online = redisUtil.getOnline(RedisConstant.ONLINE).stream().map(Object::toString).collect(Collectors.toSet());

        return new OnLineVo(setSize,online, new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }

    @Override
    public Page<UserShow> getAll(PageRo pageRo) {
        Page<UserShow> all = userAccountDao.getAll(pageRo);
        int size = userAccountRepository.findAll().size();
        all.setCount(size);
        //在线用户
        Set<String> online = redisUtil.getOnline(RedisConstant.ONLINE).stream().map(Object::toString).collect(Collectors.toSet());
        all.getContent().forEach(userShow -> {
            String s = online.contains(userShow.getId()) ? "在线" : "不在线";
            userShow.setIsOnline(s);
        });
        return all;
    }

    @Override
    public UserVo getUserAccount(String userId) {
       return userAccountRepository.findByIdAndDataStatus(userId, DataStatusEnum.EFFICIENT.getCode()).map(userAccount -> {
            UserVo userVo = new UserVo();
            //设置角色名字
            List<RoleVo> roleName = roleDao.getRole(userAccount.getId()).stream().map(role -> new RoleVo(role.getId(),role.getRoleName())).collect(Collectors.toList());
            userVo.setId(userAccount.getId());
            userVo.setAccount(userAccount.getAccountNumber());
            userVo.setEmail(userAccount.getEmail());
            userVo.setName(userAccount.getAccountName());
            userVo.setAvatar(userAccount.getAvatar());
            userVo.setRolas(roleName);
            return userVo;
        }).orElseThrow(()->ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_NULL));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setAvatar(String userId ,String fileId) {
        userAccountRepository.findByIdAndDataStatus(userId, DataStatusEnum.EFFICIENT.getCode()).ifPresentOrElse(userAccount -> {
            userAccount.setAvatar(fileId);
        },()->{
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_NULL);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void modifyPassword(RegisterRo registerRo) {
        userAccountRepository.findByEmailAndDataStatus(registerRo.getEmail(), 1).ifPresentOrElse(userAccount -> {
            validateCodeService.checkCode(userAccount.getEmail(), registerRo.getVer_code());
            userAccount.setPassWord(MD5Util.encode(registerRo.getPassword()));
        },()->{
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_NULL);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setName(String userId, String nowName) {
        userAccountRepository.findByIdAndDataStatus(userId, DataStatusEnum.EFFICIENT.getCode()).ifPresentOrElse(userAccount -> {
            userAccount.setAccountName(nowName);
        },()->{
            throw ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_NULL);
        });
    }

    @Override
    public void forcedExit(List<String> userIds) {
        userIds.forEach(WebSocketServer::logOut);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(List<String> userIds) {
        userAccountRepository.findAllByIdIn(userIds).forEach(userAccount -> {
            userAccount.setDataStatus(userAccount.getDataStatus().equals(1) ? 0 : 1);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setPromise(PromiseRo promiseRo, String userId) {
        userAccountRepository.findByIdAndDataStatus(userId, 1)
                .ifPresent(userAccount -> {
                    try {
                        Class<? extends UserAccount> aClass = userAccount.getClass();
                        Field declaredField = aClass.getDeclaredField(promiseRo.getPromiseType().name());
                        declaredField.setAccessible(true);

                        Integer o = (Integer) declaredField.get(userAccount);
                        declaredField.set(userAccount, o==1?0:1);
                    }catch (Exception e){
                        log.error(e.getMessage());
                    }
                });
    }

    @Override
    public UserPromiseVo getPromise(String userId) {
        return userAccountRepository.findByIdAndDataStatus(userId,1).map(userAccount -> {
            UserPromiseVo userPromiseVo = new UserPromiseVo();
            userPromiseVo.setAutoAgree(intToBoolean(userAccount.getAutoAgree()));
            userPromiseVo.setRefuseAgree(intToBoolean(userAccount.getRefuseAgree()));
            userPromiseVo.setRefuseGroupAgree(intToBoolean(userAccount.getRefuseGroupAgree()));
            userPromiseVo.setIntoShow(intToBoolean(userAccount.getIntoShow()));
            userPromiseVo.setAutoGroupAgree(intToBoolean(userAccount.getAutoGroupAgree()));
            return userPromiseVo;
        }).orElseThrow(()->ExceptionPropertiesUtil.getException(ExceptionConstant.USER_ACCOUNT_NULL));
    }

    private boolean intToBoolean(int value){
        return value == 1;
    }

    private void send(String email){
        //保存需要发送的验证码
        ValidateCode nowValidateCode = validateCodeService.saveValidateCode(email);
        //获取模板发送验证码
        templateRepository.findByType(TemplateType.VERIFICATION_CODE).ifPresentOrElse(template -> {
            String format = String.format(template.getTemplate(), nowValidateCode.getCode());

            //构建邮件传输对象
            EmailSendDto emailSendDto = new EmailSendDto();
            emailSendDto.setEmailToList(Lists.newArrayList(email));
            emailSendDto.setSubject(template.getSubject());
            emailSendDto.setMailText(format);

            //发送
            emailService.sendEmail(emailSendDto);

            //线程提交的任务没法自己添加 需要save
            nowValidateCode.setSend(true);
            validateCodeRepository.save(nowValidateCode);
        },() -> {
           throw  ExceptionPropertiesUtil.getException(ExceptionConstant.TEMPLATE_NULL);
        });
    }
}
